import 'dart:math';
import 'dart:ui';

import 'package:canvas_clock/clock.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';

class CompositedClock extends MultiChildRenderObjectWidget {
  final Animation<double> spinUpAnimation;

  /// The [children] need to cover each component type in [ClockComponent], which can be specified in the [RenderObject.parentData] using [ClockChildrenParentData].
  /// Every component can only exist exactly once.
  /// Notice that the order of [children] does not affect the layout or paint order.
  CompositedClock({
    Key? key,
    required this.spinUpAnimation,
    required List<Widget> children,
  }) : super(key: key, children: children);

  @override
  RenderObject createRenderObject(BuildContext context) {
    return RenderCompositedClock(
      spinUpAnimation: spinUpAnimation,
    );
  }
}

enum ClockComponent {
  analogTime,
  background,
  ball,
  date,
  digitalTime,
  location,

  /// Slide where the [ball] rolls down.
  slide,
  temperature,
  weather,
}

/// The order in which the children are passed to the widget does not matter
/// and is alphabetical, i.e. the following is the paint order.
const List<ClockComponent> paintOrder = [
  ClockComponent.background,
  ClockComponent.location,
  ClockComponent.date,
  ClockComponent.temperature,
  ClockComponent.weather,
  ClockComponent.analogTime,
  ClockComponent.digitalTime,
  ClockComponent.ball,
  ClockComponent.slide,
];

class ClockChildrenParentData
    extends CompositionChildrenParentData<ClockComponent> {
  bool hasSemanticsInformation = false;
}

class RenderCompositedClock extends RenderComposition<ClockComponent,
    ClockChildrenParentData, CompositedClock> {
  final Animation<double> spinUpAnimation;

  RenderCompositedClock({
    required this.spinUpAnimation,
  }) : super(ClockComponent.values);

  @override
  bool get isRepaintBoundary => false;

  @override
  void attach(PipelineOwner owner) {
    super.attach(owner);
    spinUpAnimation.addListener(markNeedsLayout);
  }

  @override
  void performLayout() {
    final background = children[ClockComponent.background.index] as RenderBox?;
    if (background != null) {
      background.layout(BoxConstraints.tight(size));
    }

    final slide = children[ClockComponent.slide.index] as RenderBox?;
    if (slide != null) {
      slide.layout(
        BoxConstraints.tight(Size(
          size.width / 2.5,
          size.height / 2.5,
        )),
      );
    }

    final ball = children[ClockComponent.ball.index] as RenderBox?;
    if (ball != null) {
      final ballRect = Rect.fromLTWH(
        0,
        0,
        size.width / 8,
        size.width / 8,
      );
      ball.layout(BoxConstraints.tight(ballRect.size), parentUsesSize: false);
    }

    final analogTime = children[ClockComponent.analogTime.index] as RenderBox?;
    if (analogTime != null) {
      final analogTimeSize = Size.square(size.width / 2.2);
      analogTime.layout(BoxConstraints.tight(analogTimeSize));
    }

    final weather = children[ClockComponent.weather.index] as RenderBox?;
    if (weather != null) {
      final weatherSize = Size(size.width / 3.2, size.height / 3.2);
      weather.layout(BoxConstraints.tight(weatherSize), parentUsesSize: false);
      final weatherData = weather.parentData as ClockChildrenParentData?;
      if (weatherData != null) {
        weatherData.offset = Offset(
          size.width - weatherSize.width - size.width / 24,
          size.height / 24,
        );
      }
    }
  }

  @override
  void detach() {
    spinUpAnimation.removeListener(markNeedsLayout);
    super.detach();
  }

  @override
  void setupParentData(RenderObject child) {
    if (child.parentData is! ClockChildrenParentData) {
      child.parentData = ClockChildrenParentData();
    }
  }

  @override
  void paint(PaintingContext context, Offset offset) {
    void visitor(RenderBox? child) {
      if (child != null) {
        final childParentData = child.parentData as ClockChildrenParentData?;
        if (childParentData != null) {
          context.paintChild(child, childParentData.offset + offset);
        }
      }
    }

    for (final component in paintOrder) {
      final child = children[component.index] as RenderBox?;
      if (child != null) {
        visitor(child);
      }
    }
  }
}
